package com.bluerox.updating;

import java.io.File;
import java.io.FilenameFilter;

import org.jdom.Document;
import org.jdom.Element;

/**
 * 程序升级类，主要处理程序升级的各项操作<br><br>
 * 
 * 服务器存储的为数据文件的压缩包，命名规则为：<br>
 * 原文件名.zip 此处的版本号为下划线格式 <br>
 * 例：原文件名为start.bin,压缩包文件名为 start.bin.zip<br>
 * <br>
 * 
 * 状态文件名为:<br>
 * 状态文件的MD5.xml<br>
 * <br>
 * 
 * 状态文件的压缩包命名规则为：<br>
 * 程序名_status.xml.zip<br>
 * 例： race_status.xml.zip<br>
 * <br>
 * 
 * 级别索引文件的命名规则为：<br>
 * 程序名_level_级别.xml<br>
 * 例： race_level_1.xml<br>
 * race_level_2.xml<br>
 * <br>
 * 
 * 状态文件格式<br>
 * <br>
 * 
 * files 根节点<br>
 * version 版本号<br>
 * top-status 整个索引文件的主状态<br>
 * level 对应的级别<br>
 * status 对应级别的状态 <br>
 * md5-1234567890abcdef 对应索引文件中的md5码，status代表文件的当前状态, pending,download,finish,size代表该文件的大小单位为KB<br>
 * <br>
 * 
 * <pre>
 * &lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>
 * &lt;files&gt;<br>
 * 	&lt;version&gt;1.0.0&lt;/version&gt;<br>
 * 	&lt;top-status&gt;pending&lt;/top-status&gt;<br>
 * 	&lt;level value='1'&gt;<br>
 * 		&lt;status&gt;pending&lt;/status&gt;<br>
 * 		&lt;md5-c78605dc24d37a4523695cbd19d53708 status='pending' size='1024'/&gt;<br>
 * 		&lt;md5-5c6ceb2591b1b80cf2fb3779fe48ceaf status='pending' size='512'/&gt;<br>
 * 		&lt;md5-4e6a49f973e434e59d61e24240067842 status='pending' size='128'/&gt;<br>
 * 	&lt;/level&gt;<br><br>
 * 	&lt;level value='2'&gt;<br>
 *  	&lt;status&gt;pending&lt;/status&gt;<br>
 * 		&lt;md5-c78605dc24d37a4523695cbd19d53708 status='pending' size='1024'/&gt;<br>
 * 		&lt;md5-5c6ceb2591b1b80cf2fb3779fe48ceaf status='pending' size='512'/&gt;<br>
 * 		&lt;md5-4e6a49f973e434e59d61e24240067842 status='pending' size='128'/&gt;<br>
 * 	&lt;/level&gt;<br>
 *  ...
 * &lt;/files&gt;<br><br>
 * 
 * 主索引文件格式<br><br>
 * program 索引文件跟节点<br>
 * title 程序名<br>
 * version 程序版本号<br>
 * max-level 程序最大级别数, 该节点只在主索引文件中存在<br>
 * root 数据文件的根节点，属性name为根目录的名称<br>
 * directory 文件夹，属性name为文件夹名称<br>
 * file 文件，属性name为文件名<br>
 * md5 文件对应的md5码<br>
 * directory和file的相对结构表示数据文件的目录结构<br><br>
 * 
 * &lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>
 * &lt;program&gt;<br>
 * &lt;title&gt;race&lt;/title&gt;<br>
 * &lt;version&gt;1.0.0&lt;/version&gt;<br>
 * &lt;max-level&gt;2&lt;/max-level&gt;<br>
 * &lt;root name='race'&gt;<br>
 * 	&lt;directory name='car'&gt;<br>
 * 		&lt;directory name='bmw'&gt;<br>
 * 			&lt;file name='bmw_data.bin.zip'&gt;<br>
 * 				&lt;md5&gt;c78605dc24d37a4523695cbd19d53708&lt;/md5&gt;<br>
 * 			&lt;/file&gt;<br>
 * 		&lt;/directory&gt;<br>
 * 		&lt;file name='data.bin.zip'&gt;<br>
 * 			&lt;md5&gt;5c6ceb2591b1b80cf2fb3779fe48ceaf&lt;/md5&gt;<br>
 * 		&lt;/file&gt;<br>
 * 	&lt;/directory&gt;<br>
 * 	&lt;file name='start.bin.zip'&gt;<br>
 * 		&lt;md5&gt;4e6a49f973e434e59d61e24240067842&lt;/md5&gt;<br>
 * 	&lt;/file&gt;<br>
 * 	&lt;/root&gt;<br>
 * &lt;/program&gt;<br><br>
 * 
 * 其他级别索引文件格式<br><br>
 * &lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>
 * &lt;program&gt;<br>
 * &lt;title&gt;race&lt;/title&gt;<br>
 * &lt;version&gt;1.0.0&lt;/version&gt;<br>
 * &lt;root name='race'&gt;<br>
 * 	&lt;directory name='car'&gt;<br>
 * 		&lt;directory name='bmw'&gt;<br>
 * 			&lt;file name='bmw_data.bin.zip'&gt;<br>
 * 				&lt;md5&gt;c78605dc24d37a4523695cbd19d53708&lt;/md5&gt;<br>
 * 			&lt;/file&gt;<br>
 * 		&lt;/directory&gt;<br>
 * 		&lt;file name='data.bin.zip'&gt;<br>
 * 			&lt;md5&gt;5c6ceb2591b1b80cf2fb3779fe48ceaf&lt;/md5&gt;<br>
 * 		&lt;/file&gt;<br>
 * 	&lt;/directory&gt;<br>
 * 	&lt;file name='start.bin.zip'&gt;<br>
 * 		&lt;md5&gt;4e6a49f973e434e59d61e24240067842&lt;/md5&gt;<br>
 * 	&lt;/file&gt;<br>
 * &lt;/root&gt;<br>
 * &lt;/program&gt;<br><br<br>
 * </pre>
 * 
 * Example：<br>
 * <br>
 * 
 * import com.bluerox.updating.*;<br>
 * <br>
 * 
 * 创建Entity程序实例 Entity e = new Entity();<br>
 * e.setTitle("race");<br>
 * e.setVersion("1.0.0");<br>
 * e.setBaseUrl("http://localhost:8080/");<br>
 * e.setBasePath("/home/lijianbo/test");<br>
 * <br>
 * @author kivenLi
 *
 */
public class UpdateSofe {
	//程序的实体类
	private Entity entity;
	//需要下载文件的大小 单位KB
	private int downloadFileSize = 0;
	//已经下载的文件大小
	private int hasDownloadSize = 0;
	
	public UpdateSofe(Entity e) {
		this.entity = e;;
	}
	/**
	 * 返回需要下载文件的大小
	 * @return
	 */
	public int getDownloadFileSize() {
		return this.downloadFileSize;
	}
	/**
	 * 私有方法，文件大小只能由该类的内部设定
	 * @param size 文件的大小 单位为KB
	 */
	private void setDownloadFileSzie(int size) {
		this.downloadFileSize = size;
	}
	/**
	 * 获得已经下载了的文件的大小。该接口还未实现！
	 * @return
	 */
	public int getHasDownloadSize() {
		return this.hasDownloadSize;
	}
	/**
	 * 私有方法，与上面同理
	 * @param size 下载了的文件的大小  单位为KB
	 */
	private void setHasDownloadSize(int size) {
		this.hasDownloadSize = size;
	}
	
	/**
	 * 升级程序的主入口
	 */
	public void update() {
		//主索引文件的名称
		String mainFileName = this.entity.getMainFileName();
		//状态文件压缩包的名称
		String statusZipFileName = this.entity.getStatusZipFileName();
		//创建http请求类
		HttpService service = new HttpService();
		//创建临时文件夹，和临时文件夹下的索引文件夹
		File tempFolder = new File(this.entity.getTempPath());
		
		//创建索引文件夹
		File mainFolder = new File(this.entity.getTempMainPath());
		File mainFile = new File(this.entity.getTempMainPath() + File.separator + mainFileName);
		
		if(!tempFolder.exists()) {
			System.out.println("临时文件夹不存在，创建临时文件夹");
			tempFolder.mkdirs();
		}
		
		//如果状态索引文件夹不存在，下载状态索引文件压缩包
		File statusFolder = new File(this.entity.getTempStatusPath());
		//先判断索引文件夹是否存在，如果不存在就创建这个文件夹
		if(!statusFolder.exists()) {
			//创建存放临时索引文件夹
			statusFolder.mkdirs();
		}
		
		//为了提高程序的健壮性，这里对临时状态文件夹中的文件进行过滤，只取出其中以.xml结尾的文件
		File[] statusFiles = statusFolder.listFiles(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return name.endsWith(".xml");
			}
		});
		
		 //索引文件夹中，不管程序执行到什么时候，该文件夹下最多只有一个xml文件。
		if (statusFiles.length == 0) {
			// 下载更新版本的状态索引文件压缩包
			String fileMD5 = "";
			do {
				System.out.println("状态索引文件不存在，下载状态索引文件压缩包");
				service.send(this.entity.getBaseUrl(), this.entity
						.getTempStatusPath(), statusZipFileName);

				// 解压缩状态索引文件
				System.out.println("解压缩状态索引文件");
				String zipStatusFile = this.entity.getTempStatusPath()
						+ File.separator + statusZipFileName;
				Zip.unzipFile(this.entity.getTempStatusPath(), zipStatusFile);

				//解压缩完之后将原来的状态索引文件的压缩包删除
				new File(zipStatusFile).delete();
				// 得到MD5码为文件名的状态索引文件
				statusFiles = statusFolder.listFiles();
				this.entity.setStatusFileName(statusFiles[0].getName());

				// 判断索引文件的MD5码与文件名是否一致，如不一致重新下载
				try {
					fileMD5 = MD5.getHash(this.entity.getTempStatusPath()
							+ File.separator + this.entity.getStatusFileName(),
							"MD5");
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println("索引文件的 MD5：" + fileMD5);
				System.out.println("索引文件的Name："
						+ this.entity.getStatusFileName().substring(0,
								this.entity.getStatusFileName().length() - 4));
			} while (!fileMD5.equals(this.entity.getStatusFileName().substring(
					0, this.entity.getStatusFileName().length() - 4)));
		}
		else {
			/*
			 *   解决同一个版本游戏的升级问题！！
			 *  	根据状态文件夹的版本信息控制是否为升级还是一般下载。
			 *  
			 * 如果已经存在状态文件，就进行判断当前的状态文件是否为要更新的状态文件。
			 * 判断的方法是根据状态文件中的Version信息和entity中的version信息做比较，如果version信息相同，则说明该版本没下载完成，如果版本不一样，则说明此操作是升级操作
			 * 得到当前状态文件的版本信息
			 */
			Document doc = XML.parseFile(this.entity.getTempStatusPath()+ File.separator + statusFiles[0].getName());
			String version = doc.getRootElement().getChildText("version");
			//如果判断出是当前版本号与原先版本号不相同，那么就将旧的状态索引文件删除，重新下载新的版本号状态文件压缩包
			if(!version.equals(this.entity.getVersion())) {
				//先将旧的状态文件删除再去下载
				new File(this.entity.getTempStatusPath()+ File.separator + statusFiles[0].getName()).delete();
				
				//下载新版本的状态文件
				String fileMD5 = "";
				do {
					System.out.println("新的状态索引文件不存在，下载对应的状态索引文件压缩包");
					service.send(this.entity.getBaseUrl(), this.entity
							.getTempStatusPath(), statusZipFileName);
					
					// 解压缩状态索引文件
					System.out.println("解压缩状态索引文件");
					String zipStatusFile = this.entity.getTempStatusPath()
					+ File.separator + statusZipFileName;
					Zip.unzipFile(this.entity.getTempStatusPath(), zipStatusFile);
					
					new File(zipStatusFile).delete();
					// 得到MD5码为文件名的状态索引文件，在这里也进行过滤
					statusFiles = statusFolder.listFiles(new FilenameFilter() {
						@Override
						public boolean accept(File dir, String name) {
							return name.endsWith(".xml");
						}
						
					});
					//在entiry实体类中设置新的状态索引文件名
					this.entity.setStatusFileName(statusFiles[0].getName());
					System.out.println("此时新的状态索引文件名："+this.entity.getStatusFileName());
					// 判断索引文件的MD5码与文件名是否一致，如不一致重新下载
					try {
						fileMD5 = MD5.getHash(this.entity.getTempStatusPath()
								+ File.separator + this.entity.getStatusFileName(),
						"MD5");
					} catch (Exception e) {
						e.printStackTrace();
					}
					System.out.println("状态索引文件的 MD5：" + fileMD5);
					System.out.println("状态索引文件的Name："
							+ this.entity.getStatusFileName().substring(0,
									this.entity.getStatusFileName().length() - 4));
					System.out.println("状态索引文件:" 
							+ this.entity.getStatusFileName().substring(0,
									this.entity.getStatusFileName().length() - 4));
				} while (!fileMD5.equals(this.entity.getStatusFileName().substring(
						0, this.entity.getStatusFileName().length() - 4)));
			}
			//如果判断出版本号相同，那么继续操作原先的状态文件
			else {
				this.entity.setStatusFileName(statusFiles[0].getName());
			}
		}
		
		// 解析状态索引文件
		System.out.println("解析状态索引文件");
		// Jdom解析xml文件
		Document statusDoc = XML.parseFile(this.entity.getTempStatusPath()
				+ File.separator + this.entity.getStatusFileName());
		
		// 此时的entity对象已经有了相应的属性了，其中在主程序中所创建的那些基本属性外，这里的entity已经有了statusFileName属性值了
		XML xml = new XML(this.entity);
		//存放要下载的文件的大小
		int downloadFileSize = xml.getDownloadFileSize(statusDoc);
		System.out.println("要下载的文件大小：" + downloadFileSize);
		//设定要下载的文件大小的值
		this.setDownloadFileSzie(downloadFileSize);
		
		// topStatus的是xml文件所对应的<top-status></top-status>所包围的值，在这里这个值为pending...
		String topStatus = statusDoc.getRootElement()
				.getChildText("top-status");
		
		// 状态文件中的主状态不为finish,说明程序更新未完成
		if(!topStatus.equals(Entity.FINISH)) {
			System.out.println("主状态不等于finish,程序没有更新完毕。");
			
			//判断主索引文件夹是否存在
			if(!mainFolder.exists()) {
				//主索引文件夹不存在，在创建该文件夹
				mainFolder.mkdirs();
			}
			// 如果主文件不存在，发送请求，下载主索引文件压缩包
			if(!mainFile.exists()) {
				//应该先创建方主索引文件的文件夹
				System.out.println("主文件不存在，下载主索引文件压缩包");
				//发送要下载的信息到服务端，然后进行下载
				service.send(this.entity.getBaseUrl(), this.entity
						.getTempMainPath(), mainFileName + Entity.ZIP_EXT);

				// 解压缩主索引文件
				System.out.println("解压缩主索引文件");
				String zipMainFile = this.entity.getTempMainPath() + File.separator
						+ mainFileName + Entity.ZIP_EXT;
				Zip.unzipFile(this.entity.getTempMainPath(), zipMainFile);

				 //解压缩完索引文件就应该将索引文件删除，省用户的内存空间
				new File(zipMainFile).delete();
			}
			
			// 这里将解析成Document对象的statusDoc传到DownloadFiles中
			DownloadFiles df = new DownloadFiles(this.entity, statusDoc);
			
			//解析主索引文件
			System.out.println("解析主索引文件");
			Document mainDoc = XML.parseFile(this.entity.getTempMainPath() 
					+ File.separator + mainFileName);
			//获得主索引文件中的<max-level>值
			int maxLevel = Integer.parseInt(mainDoc.getRootElement()
					.getChildText("max-level"));
			//根节点
			Element root = mainDoc.getRootElement().getChild("root");
			//级别标志
			Integer levelFlag = 1;
			//判断级别为1的,也就是主索引所对应状态文件中的<status>,这里的statusValue有三个状态download，pending
			//String statusValue = xml.getStatusByLevelValue(statusDoc, levelFlag.toString());将statusValue，存放的是字符串的引用，因为状态文件的内容是随时改变的
			String statusValue = new String(xml.getStatusByLevelValue(statusDoc, levelFlag.toString()));
			
			//如果级别为1的状态为pending，说明未完成文件的下载
			if(statusValue.equals(Entity.PENDING)) {
				System.out.println("该级别的状态为pending,即未下载完成，下载此级别的文件");
				//下载的目录是放在/race/temp文件夹下，不是放在/race/temp/level文件夹下
				df.download(root, this.entity.getTempPath()
						+ File.separator + root.getAttributeValue("name"),levelFlag.toString());
				
				//更新状态文件中当前级别的状态为download
				xml.setStatusByLevelValue(statusDoc, levelFlag.toString(),"download");
				xml.save(statusDoc, this.entity.getTempStatusPath()
						+ File.separator + this.entity.getStatusFileName());
				
			}
			//如果级别为1的状态为download,说明下载完成，但是还没移动到正确的文件夹下
			//由于状态文件是动态改变的，不能写成if(statusValue.equals(Entity.DOWNLOAD))进行条件判断
			if(xml.getStatusByLevelValue(statusDoc, levelFlag.toString()).equals(Entity.DOWNLOAD)) {
				System.out.println("该级别的状态为download,即下载完成，移动文件到正确的目录上");
				MoveFiles mf = new MoveFiles(this.entity,statusDoc,root,levelFlag.toString());
				
				try {
					mf.moveWithLevelValue(this.entity.getTempPath() + File.separator
							+ root.getAttributeValue("name"), 
							this.entity.getBasePath() + File.separator + this.entity.getTitle());
					
					
					//将所有的文件移动完之后，改变该级别下的状态文件的状态属性
					xml.setStatusByLevelValue(statusDoc, levelFlag.toString(), "finish");
					xml.save(statusDoc, this.entity.getTempStatusPath() + File.separator + this.entity.getStatusFileName());
					
					//删除临时文件夹
					new File(this.entity.getTempPath() + File.separator + 
								root.getAttributeValue("name")).delete();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
			/*
			 * 操作完一个级别的文件后将级别标志自增,但是这个递增不能放在if语句块里面。
			 * 如果方里面则会出现逻辑错误，因为如果statusValue的状态已经不是Pending的情况，那么这个if语句将不被执行，然而levelFlag的值依然是1，
			 * 这样就会导致程序无法指向下面的其他级别的下载，则出现逻辑错误。
			 */
			levelFlag++;
			
			//如果当前级别大于1并且小于等于最高级别，循环操作下载其他级别的文件
			if(levelFlag > 1 && levelFlag <= maxLevel) {
				System.out.println("当前级别大于1并且小于等于最高级别，循环操作下载其他级别的文件");
				for(int i=levelFlag; i<=maxLevel; i++) {
					//设定下个级别的名称
					String levelFileName = this.entity.getNextFileName(i - 1);
					File levelFile = new File(this.entity.getTempMainPath() + File.separator + levelFileName);
					// 判断如果此级别索引文件不存在，下载并解压
					if(!levelFile.exists()) {
						System.out.println("级别" + i + "文件"
								+ levelFileName + "不存在，下载并解压");
						//下载该级别的压缩文件
						service.send(this.entity.getBaseUrl(), this.entity.getTempMainPath()
								, levelFileName + Entity.ZIP_EXT);
						
						String zipLevelFile = this.entity.getTempMainPath() + File.separator 
												+ levelFileName + Entity.ZIP_EXT;
						//解压缩该级别的文件
						Zip.unzipFile(this.entity.getTempMainPath(), zipLevelFile);
						// 解压缩完之后将Zip文件删除
						new File(zipLevelFile).delete();
					}
					
					//解析当前索引文件
					System.out.println("解析" + levelFileName);
					Document levelDoc = XML.parseFile(this.entity.getTempMainPath()
							+ File.separator + levelFileName);
					
					//在对应的级别文件中要修改相应的root值，这样才能将文件夹移动到正确的位置,现在已经转到次索引文件
					root = levelDoc.getRootElement().getChild("root");
					//root = levelDoc.getRootElement();
					
					//i+""操作的目的是为了将int类型的数据转化为String类型
					statusValue = xml.getStatusByLevelValue(statusDoc, i+"");
					
					// 如果主状态为pending，说明此级别文件没有下载完全，重新下载此级别文件中状态为pending的文件
					if(statusValue.equals(Entity.PENDING)) {
						System.out.println("状态为pending，下载级别" + i
								+ "的所有文件");
						//没有把文件下载下来
						df.download(levelDoc.getRootElement().getChild("root"), 
								this.entity.getTempPath() + File.separator
								+ levelDoc.getRootElement().getChild("root").getAttributeValue("name"),i+"");
						
						//更新状态文件中当前级别的状态为download
						xml.setStatusByLevelValue(statusDoc, i+"","download");
						xml.save(statusDoc, this.entity.getTempStatusPath()
								+ File.separator + this.entity.getStatusFileName());
					}
					
					//如果级别为i的状态为download,说明下载完成，但是还没移动到正确的文件夹下
					if(xml.getStatusByLevelValue(statusDoc, i+"").equals(Entity.DOWNLOAD)) {
						System.out.println("该级别的状态为download,即下载完成，移动文件到正确的目录上");
						MoveFiles mf = new MoveFiles(this.entity,statusDoc,root,i+"");
						
						try {
							mf.moveWithLevelValue(this.entity.getTempPath() + File.separator
									+ root.getAttributeValue("name"), 
									this.entity.getBasePath() + File.separator + this.entity.getTitle());
							
							//将所有的文件移动完之后，改变该级别下的状态文件的状态属性
							xml.setStatusByLevelValue(statusDoc, i+"", "finish");
							xml.save(statusDoc, this.entity.getTempStatusPath() + File.separator + this.entity.getStatusFileName());
							
							//删除没用的临时文件夹
							new File(this.entity.getTempPath() + File.separator + 
										root.getAttributeValue("name")).delete();
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			}
			//将所有级别的文件都已经下载到临时文件夹下时，更改索引文件的<top-status>状态为finish
			statusDoc.getRootElement().getChild("top-status").setText("finish");
			xml.save(statusDoc, this.entity.getTempStatusPath() 
					+ File.separator + this.entity.getStatusFileName());
			System.out.println("更新操作完成...");
		}
		else {
			System.out.println("主状态<top-status>的值为finish,则所有级别文件都已经更新完毕");
		}
	}
}
